// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "CaeDataChannelServer.h"
#include <string>
#include <system.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/un.h>
#include <condition_variable>
#include "../cae_common/CaeLogConfig.h"
#include "CaeChannelDataHandler.h"

const static std::string UNIX_IPC_NAME = "CLOUD_APP_DATA_CHANNEL";

CaeChannelConnection::CaeChannelConnection(int fd)
{
    mSockFd = fd;
    struct timeval timeo;
    timeo.tv_sec = 1;
    timeo.tv_usec = 0;

    if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo)) < 0) {
        LOGD("Socket setscokopt SO_SNDTIMEO failed errno: %d.", errno);
    }

    if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)) < 0) {
        LOGD("Socket setsockopt SO_RCVTIMEO failed errno: %d.", errno);
    }
}

CaeChannelConnection::~CaeChannelConnection()
{
    if (mSockFd > 0) {
        close(mSockFd);
        mSockFd = 0;
    }
}

int CaeChannelConnection::ReadN(uint8_t *buf, int length)
{
    int recvBytes = length;
    int bytes = 0;
    int retryTimes = 0;
    memset_s(buf, length, 0, length);
    while (length > 0) {
        bytes = recv(mSockFd, buf, length, 0);
        if (bytes > 0) {
            length -= bytes;
            buf += bytes;
            retryTimes = 0;
        } else {
            retryTimes++;
            if (retryTimes > 3) {
                LOGE("Socket recv error, retry times: %d", retryTimes);
                return -1;
            }
            if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
                continue;
            } else {
                LOGE("Socket errno: %d, errmsg: %s.", errno, strerror(errno));
                return -1;
            }
        }
    }
    return recvBytes;
}

int CaeChannelConnection::WriteN(uint8_t *buf, int length)
{
    int sendBytes = length;
    int bytes = 0;
    mSockMutex.lock();
    while (length > 0) {
        bytes = send(mSockFd, buf, length, 0);
        if (bytes > 0) {
            length -= bytes;
            buf += bytes;
        } else {
            LOGE("Socket errno: %d, errmsg: %s.", errno, strerror(errno));
            if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
                continue;
            } else {
                mSockMutex.unlock();
                return -1;
            }
        }
    }
    mSockMutex.unlock();
    return sendBytes;
}

CaeDataChannelServer::CaeDataChannelServer()
{
    mIsServerRun = false;
    mServerFd = 0;
}

CaeDataChannelServer::~CaeDataChannelServer() = default;

int CaeDataChannelServer::CaeDataChannelServer::Init()
{
    struct sockaddr_un sockAddr;
    mServerFd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (mServerFd <= 0) {
        LOGE("Server init failed.");
        return  -1;
    }
    sockAddr.sun_family = AF_UNIX;
    sockAddr.sun_path[0] = '\0';
    const int len = sizeof(sockAddr.sun_path) - 1;
    if (EOK != strncpy_s(sockAddr.sun_path + 1, len, UNIX_IPC_NAME.c_str(), len - 1)) {
        LOGE("Server init failed, strncpy_s UNIX_IPC_NAME failed.");
        return -1;
    }
    int ret = ::bind(mServerFd, (struct sockaddr*)&sockAddr,
            sizeof(sockAddr.sun_family) + UNIX_IPC_NAME.size() + 1);
    if (ret == -1) {
        LOGE("Server init failed, bind failed.");
        close(mServerFd);
        return -1;
    }
    ret = ::listen(mServerFd, 1);
    if (ret == -1) {
        LOGE("Server init failed, listen failed.");
        close(mServerFd);
        return -1;
    }
    bool bRet = Start();
    if (!bRet) {
        LOGE("Server thread start failed.");
        close(mServerFd);
        return -1;
    }
    return 0;
}

void CaeDataChannelServer::CaeDataChannelServer::Deinit()
{
    if (m_threadStatus == CAE_THREAD_STATUS_RUNNING) {
        mIsServerRun = false;
        Stop();
        if (mServerFd > 0) {
            close(mServerFd);
            mServerFd = 0;
        }
    }
}

void CaeDataChannelServer::Run()
{
    char threadName[] = "DataChannel";
    prctl(PR_SET_NAME, threadName);

    mIsServerRun = true;
    int clientSockfd;
    struct sockaddr_un client;
    socklen_t len = sizeof(client);
    while (mIsServerRun) {
        clientSockfd = ::accept(mServerFd, (struct  sockaddr*)&(client), &len);
        if (clientSockfd == -1) {
            LOGE("Server accept failed.");
            continue;
        } else {
            LOGI("New channel client connect.");
            CreateDispatchHandler(clientSockfd);
        }
    }
}

void CaeDataChannelServer::CreateDispatchHandler(int sockfd) {
    auto *channelConnection = new(std::nothrow) CaeChannelConnection(sockfd);
    if (channelConnection == nullptr) {
        close(sockfd);
        return;
    }

    auto *recvHandler = new(std::nothrow) CaeChannelDataRecvHandler(channelConnection);
    if (recvHandler == nullptr) {
        delete channelConnection;
        channelConnection = nullptr;
        return;
    }

    auto *sendHandler = new(std::nothrow) CaeChannelDataSendHandler(channelConnection);
    if (sendHandler == nullptr) {
        delete channelConnection;
        channelConnection = nullptr;
        delete recvHandler;
        recvHandler = nullptr;
        return;
    }

    LOGI("Start channel data dispatch.");
    recvHandler->Start();
    sendHandler->Start();

    while (recvHandler->m_threadStatus == CAE_THREAD_STATUS_RUNNING
    && sendHandler->m_threadStatus == CAE_THREAD_STATUS_RUNNING) {
        usleep(100000); //100ms
    }
    LOGI("Stop channel data dispatch.");
    recvHandler->Stop();
    sendHandler->Stop();

    delete recvHandler;
    recvHandler = nullptr;
    delete sendHandler;
    sendHandler = nullptr;
    delete channelConnection;
    channelConnection = nullptr;
}
